/**
 * 
 */


import java.io.*;
import java.util.*;


import gnu.io.*;
//import javax.comm.*;

/**
 * @author andipandi
 *
 */
public class GatewayClient extends Thread implements SerialPortEventListener{
	
	/**
	 * public attributes
	 */
	public long timeout_millis = 60000; //initial 1min
	
	/**
	 * supported data types
	 * 
	 * @see ScatterWeb.Net.PacketTypes.h
	 */
	private static final byte DATA_TYPE_INFO   = 13; ///<information package
	private static final byte DATA_TYPE_RECORD = 12; ///<fragment of infrasonic record
	
	private static final byte INFO_PACKET_SIZE = 72; ///<4bytes header + 68bytes data
	
	/**
	 * states of reading Packets
	 */
	private static final byte STATE_IDLE        = 0; ///<no packet receiving
	private static final byte STATE_INFO_PACKET = 1; ///<receiving an info_packet
	private static final byte STATE_DATA_PACKET = 2; ///<receiving a data_packet
	private static final byte STATE_TEXT_PACKET = 3; ///<receiving a txt message
	private static final byte STATE_HEADER      = 4; ///<reading header
	
	private int state = STATE_IDLE;                  ///< the current state
	private int size  = 0;                           ///< the size of the current packet
	
	private byte[] buffer = new byte[256]; ///< the read buffer
	private int bufferIdx = 0;             ///< index of current buffer pos
	private boolean connected = false;
	/**
	 * any error is written to this string
	 */
	public String error = "";
	
	//called if data is received
	private GatewayListener listener;

	/**
	 * comm port
	 */
	CommPortIdentifier thePortID;
	SerialPort serialPort;
	DataInputStream in;
	DataOutputStream out;
	private boolean isOpen = false;
	
	/**
	 * all nodes which sent an INFO packet
	 */
	public Hashtable<String, NodeRecorder> nodes = new Hashtable<String, NodeRecorder>();

	
		/**
	 * constructor
	 * @param gl called on incomming data or error
	 */
	public GatewayClient(GatewayListener gl){
		listener = gl;
		this.start();
	}
	
	
	/**
	 * checks the timeout of all node recordes every six seconds(D3 slot period)
	 */
	public void run(){
		while(true){
		
			//System.out.print("check timeout...");
			Enumeration<NodeRecorder> elems = nodes.elements();
			while(elems.hasMoreElements()){
				elems.nextElement().checkTimeout(timeout_millis);
			}
			
			try{
				Thread.sleep(6000);
			}catch(Exception e){
				listener.msgEvent("GatewayClient:"+ e.getMessage());
			}
		}
		
	}
	
	/**
	 * opens com port to gateway node
	 * @param comPrtName name of com port
	 * 
	 * @return true if success, false else (error msg is written in this.error)
	 */
	public boolean open(String comPrtName){
		

		
		isOpen = false;
		error = "";
		CommPortIdentifier portID;
		thePortID = null;
		Enumeration portList = CommPortIdentifier.getPortIdentifiers();
		//find com port
	    while (portList.hasMoreElements()) {
	    	portID = (CommPortIdentifier) portList.nextElement();
	      	if(    (portID.getPortType() == CommPortIdentifier.PORT_SERIAL)
	      		&& (portID.getName().equals(comPrtName)) ) {
	      			thePortID = portID;
	      			break;
	      	}
	    }
		if(thePortID == null ){
			error = "Can't find " + comPrtName + ". Maybe it is no Serial port.";
			return false;
		}
		
	    try {
	        serialPort = (SerialPort) thePortID.open("InfrasoundGateway", 2000);
	    } catch (PortInUseException e) {
	    	error = "Can't open COM port: port in use.";
	    	return false;
	    }
	    try {
	    	in = new DataInputStream(serialPort.getInputStream());
	    	out = new DataOutputStream(serialPort.getOutputStream());
	    } catch (IOException e) {
	    	error = "COM Port Error " + e.getMessage();
	    	return false;
	    }
	    try {
	        serialPort.addEventListener(this);
	    } catch (TooManyListenersException e) {
	    	error = "COM Port listening error";
	    	return false;
	    }
	    serialPort.notifyOnDataAvailable(true);
	    try {
	        serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
	    } catch (UnsupportedCommOperationException e) {
	    	error = "COM Port initialization error " + e.getMessage();
	    	return false;
	    }
	    isOpen = true;
	    return true;
	}
	
	/**
	 * 
	 * @return true if success, false else (error msg is written in this.error)
	 */
	public boolean close(){
		
		connected = false;
		error = "";
		if(!isOpen){
			error = "No open COM port.";
			return false;
		}
	    try {
	    	in.close();
	    	out.close();
	    } catch (IOException e) {
	    	error = "COM Port Error " + e.getMessage();
	    	return false;
	    }
	    isOpen = false;
	    serialPort.close();
		return true;
	}
	
	/**
	 * handles serial events
	 * @param ev
	 */
	int counter = 0;
	public void serialEvent(SerialPortEvent ev){
		
		switch(ev.getEventType()){
			case SerialPortEvent.DATA_AVAILABLE:
				try {
					byte c = 0;
					//bufferIdx = 0;
					//size = 0;
					
					while(in.available()>0){
						
						
						// new data packet
						if( state == STATE_IDLE ){

							

							c =in.readByte();// & 0xFF);
							
							// new info or data packet
							if((0xFF&c) == 0xFF ){
								state = STATE_HEADER;
								//System.out.println(counter + ":begin dat");
								//print text received before
								if(bufferIdx>0){
									buffer[bufferIdx] = 0;
									String str = new String(buffer);
									listener.msgEvent(str.substring(0, bufferIdx)  );
									bufferIdx=0;
								}
								size = 0;
								
							}
							else {//text message
								//System.out.println(counter + ":begin text");
								buffer[bufferIdx++]=c;
								//System.out.println("new text msg");
								//state = STATE_TEXT_PACKET;
								
								//if buffer full or new line received just print it
								if(bufferIdx>=255 || c == 10){
									buffer[bufferIdx] = 0;
									String str = new String(buffer);
									listener.msgEvent(str.substring(0, bufferIdx));

									bufferIdx=0;
								}
							}
							counter++;
							continue;
						}
						
						//read header
						if( state == STATE_HEADER){
							c = in.readByte();// & 0xFF);
							bufferIdx = 0;
							if(size == 0 ){
								size = (0xFF&c) - 2; //four bytes header (without type & size)
							}	
							else if( c == DATA_TYPE_INFO ){
								//System.out.println(counter + ":begin info pkt");
								state = STATE_INFO_PACKET;
								//System.out.println("new InfoPacket");
								if(size != 79){
									listener.msgEvent("InfoHeaderError: wrong size="+ size);
									bufferIdx = 0;
									size = 0;
									state = STATE_IDLE;
								}
							}
							// new data packet
							else if( c == DATA_TYPE_RECORD ){
								//System.out.println(counter + ":begin data pkt");
								state = STATE_DATA_PACKET;
								//System.out.println("new DataPacket");
								if(size != 113){
									listener.msgEvent("DaTaHeaderError: wrong size="+ size);
									bufferIdx = 0;
									size = 0;
									state = STATE_IDLE;
								}
							}
							else{
								System.out.println(counter +":header error." );		
								bufferIdx = 0;
								size = 0;
								state = STATE_IDLE;
							}
							counter++;
							continue;
						}
											
						//receive data or info packet
						if((bufferIdx < size)){
							c = in.readByte();
							//System.out.println(counter + ":" + (0xFF&c));
							counter++;
							buffer[bufferIdx++] = c;
						}
				
						//packet complete?
						if( bufferIdx >= size  && size != 0){
							if( state == STATE_INFO_PACKET ){
								parseInfoPacket();
							}
							else if( state == STATE_DATA_PACKET ){
								
								int nodeID = (0xFC&buffer[2]) >> 2;
								int sqr_nr = (((int)(0x3&buffer[2]))<<8) + (0xFF&buffer[1]);
								char type = (char)(0xFF&buffer[0]);
								
								//System.out.println((0xFF&buffer[1]) + "," + (0xFF&buffer[2]) +" id " + nodeID + " new seg " + sqr_nr);
								NodeRecorder n = nodes.get(String.valueOf(nodeID));

								//new node?
								if(n == null){
									n = new NodeRecorder(nodeID, listener);
									nodes.put(String.valueOf(nodeID), n);
									connected = true;
									
								}
								listener.newNodeEvent(nodeID);
								Segment s = new Segment(sqr_nr, type, buffer, 3, 110);
								//System.out.print( counter + ":");
								n.writeSegment(s);
								listener.dataEvent(nodeID);
								
							}else
								System.out.println("parse error");
							
							bufferIdx = 0;
							size = 0;
							state = STATE_IDLE;
						}
						
						
					}
					//System.out.println(counter + ":no more data");
				}catch (IOException e) {
			    	error = "COM Port Error " + e.getMessage();
			    	listener.gatewayErrorEvent();
			    	return;
			    }
				
		}
	}
	
	private void parseInfoPacket(){
		
		
		int node_id = (buffer[2] >> 2) & 0x3F;
		int sqr_nr = (((int)(0x3&buffer[2]))<<8) + (0xFF&buffer[1]);
		//System.out.println(" from node [" + node_id + "]");
		InfoPacket infPacket = new InfoPacket();
		
		int i = 3;
		
		infPacket.node_type = (int)buffer[i++] & 0xFF;	
		infPacket.cost = (int)buffer[i++] & 0xFF; 	            
		infPacket.neighbours  = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_sd_paging = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		
		//packet statistic
		infPacket.num_rx_dat = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_rx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_rx_time = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_rx_vAck = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_rx_eAck = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 

		infPacket.num_tx_dat = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_tx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_tx_eAck = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_lost_races = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_retransmissions = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 

		//errors and warnings
		infPacket.num_errors = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.num_warnings = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.last_error = (int)buffer[i++] & 0xFF; 
		infPacket.last_warning = (int)buffer[i++] & 0xFF; 

		//time statistics
		infPacket.num_waitcycles = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.child_time_offset_max = (short)((buffer[i++]& 0xFF) + ((buffer[i++]& 0xFF) << 8));
		infPacket.stime_last_tx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_tx_time = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_tx_eack = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_tx_dat = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_rx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_rx_time = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_rx_eack = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_last_rx_dat = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_max_rx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_max_rx_time = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_max_rx_eack = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_max_rx_dat = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_min_rx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_min_rx_time = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_min_rx_eack = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.stime_min_rx_dat = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.time_adv_slot = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.time_dat_slot = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.time_last_rx_adv = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i++] & 0xFF)) << 8); 
		infPacket.time_last_delay = (int)(buffer[i++] & 0xFF) + (((int)(buffer[i] & 0xFF)) << 8); 

		NodeRecorder n;
		if(!nodes.containsKey(String.valueOf(node_id))){
			n = new NodeRecorder(node_id, listener);
			n.infoPacket = infPacket;
			nodes.put(String.valueOf(node_id), n);
			connected = true;
			
		}else{
			n = nodes.get(String.valueOf(node_id));
			n.infoPacket = infPacket;
		}
		listener.newNodeEvent(node_id);
		//create dummy
		Segment dummy = new Segment(sqr_nr, Segment.SEG_TYPE_DUMMY, null, 0, 0);
		n.writeSegment(dummy);
		
	}
	
	
	/**
	 * sends command to gateway to send an interest packet
	 * @return true if success, false else (error msg is written in this.error)
	 */
	public boolean connect(){
		counter = 0;
		error = "";
		try{
			out.writeBytes("interest\n");
			out.flush();
			
			//Iterator<NodeRecorder> it = nodes.values().iterator();
			//while(it.hasNext())
				//it.next().close();
			
			
			//nodes.clear();
		}catch (IOException e) {
	    	error = "COM Port Error " + e.getMessage();
	    	return false;
	    }
		return true;
	}
	
	/**
	 * 
	 * @param nodeID
	 * @return node details
	 */
	public NodeRecorder getNode(int nodeID){
		return nodes.get(String.valueOf(nodeID));
	}

}
